package com.biz.primus.ms.sms.service.internal;

import com.biz.primus.model.misc.enums.ApiExceptionType;
import com.biz.primus.model.misc.exception.ApiException;
import com.biz.primus.model.misc.vo.ChannelBasedAuth;
import com.biz.primus.model.misc.vo.ChannelVo;
import com.biz.primus.ms.base.service.AbstractBaseService;
import com.biz.primus.ms.sms.dao.po.CenterChannel;
import com.biz.primus.ms.sms.dao.repository.ChannelRepository;
import com.biz.primus.ms.sms.projection.ChannelCheckProjection;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * 本地(本应用)存储channel,维护状态
 *
 * @author yanweijin
 * @date 2017/5/31
 */
@Service
@Slf4j
public class ChannelService extends AbstractBaseService {


    private final ChannelRepository channelRepository;

    private LoadingCache<String, String> channelNameCache = CacheBuilder.newBuilder().expireAfterWrite(10, TimeUnit.MINUTES).build(
            new CacheLoader<String, String>() {
                @Override
                public String load(String key) throws Exception {
                    Optional<CenterChannel> optional = channelRepository.findByChannelCode(key);
                    if (!optional.isPresent()) return "not found";
                    return optional.get().getChannelName();
                }
            }
    );

    private LoadingCache<String, Optional<ChannelCheckProjection>> channelProjectionCache = CacheBuilder.newBuilder()
            .expireAfterWrite(5, TimeUnit.SECONDS).build(new CacheLoader<String, Optional<ChannelCheckProjection>>() {
                @Override
                public Optional<ChannelCheckProjection> load(String key) throws Exception {
                    return channelRepository.findByChannelCodeAsProjection(key);
                }
            });

    @Autowired
    public ChannelService(ChannelRepository channelRepository) {
        this.channelRepository = channelRepository;
    }


    public String saveChannel(ChannelVo channelVo) {
        Optional<CenterChannel> optional = channelRepository.findByChannelCode(channelVo.getChannelCode());
        if (optional.isPresent()) {
            throw new IllegalStateException("渠道code=[" + channelVo.getChannelCode() + "]已存在");
        }
        CenterChannel cc = optional.or(new CenterChannel());
        cc.setEnable(channelVo.isEnable());
        cc.setChannelName(channelVo.getChannelName());
        cc.setChannelCode(channelVo.getChannelCode());
        if (StringUtils.isNotBlank(channelVo.getToken())) {
            cc.setToken(channelVo.getToken());
        }
        //新渠道如果没有设置token,生成一个
        if (StringUtils.isBlank(channelVo.getToken())) {
            cc.setToken(UUID.randomUUID().toString().replace("-", ""));
        }
        channelRepository.save(cc);
        return cc.getToken();
    }


    public String updateChannel(ChannelVo channelVo) {
        Optional<CenterChannel> optional = channelRepository.findByChannelCode(channelVo.getChannelCode());
        if (!optional.isPresent()) {
            throw new IllegalStateException("渠道code=[" + channelVo.getChannelCode() + "]不存在");
        }
        CenterChannel cc = optional.or(new CenterChannel());
        cc.setEnable(channelVo.isEnable());
        cc.setChannelName(channelVo.getChannelName());
        cc.setChannelCode(channelVo.getChannelCode());
        if (StringUtils.isNotBlank(channelVo.getToken())) {
            cc.setToken(channelVo.getToken());
        }

        channelRepository.save(cc);
        return cc.getToken();
    }

    public boolean isEnable(String channelCode) {
        Optional<ChannelCheckProjection> projection = channelProjectionCache.getUnchecked(channelCode);
        return projection.isPresent() && projection.get().isEnable();
    }

    public void changeChannelState(String channelCode, boolean enable) {
        Optional<CenterChannel> optional = channelRepository.findByChannelCode(channelCode);
        if (!optional.isPresent()) {
            throw new NullPointerException("没有这个渠道channelCode=" + channelCode);
        }
        CenterChannel cc = optional.get();
        cc.setEnable(enable);
        channelRepository.save(cc);
    }

    public String getChannelNameFromCache(String channelCode) {
        try {
            return channelNameCache.get(channelCode);
        } catch (ExecutionException e) {
            log.error("装载channel name缓存时出现异常,msg={}", e.getMessage(), e);
            return "error";
        }
    }

    public void checkEnableAndMatchedToken(ChannelBasedAuth cba) throws ApiException {
        if (StringUtils.isBlank(cba.getChannelCode())) {
            throw new ApiException(ApiExceptionType.CHANNEL_CODE_ERROR);
        }
        Optional<ChannelCheckProjection> projection = channelProjectionCache.getUnchecked(cba.getChannelCode());
        if (!projection.isPresent()) {
            throw new ApiException(ApiExceptionType.CHANNEL_CODE_ERROR);
        }
        if (!projection.get().isEnable()) {
            throw new ApiException(ApiExceptionType.CHANNEL_DISABLED);
        }
        if (!Objects.equals(projection.get().getToken(), cba.getToken())) {
            throw new ApiException(ApiExceptionType.TOKEN_ERROR);
        }
    }

    public List<ChannelVo> getChannelList() {
        List<CenterChannel> channels = channelRepository.findAll();
        return Lists.transform(channels, new Function<CenterChannel, ChannelVo>() {
            @Override
            public ChannelVo apply(CenterChannel cc) {
                ChannelVo vo = new ChannelVo();
                vo.setChannelName(cc.getChannelName());
                vo.setToken(cc.getToken());
                vo.setEnable(cc.isEnable());
                vo.setChannelCode(cc.getChannelCode());
                return vo;
            }
        });
    }


}
